package com.jzy.gulimall.seckill.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.TypeReference;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.IdUtil;
import com.jzy.common.to.mq.SeckillOrderTo;
import com.jzy.common.utils.R;
import com.jzy.common.vo.MemberRespVo;
import com.jzy.gulimall.seckill.constant.SeckillConstant;
import com.jzy.gulimall.seckill.feign.CouponFeignService;
import com.jzy.gulimall.seckill.feign.ProductFeignService;
import com.jzy.gulimall.seckill.interceptor.UserLoginInterceptor;
import com.jzy.gulimall.seckill.service.SeckillService;
import com.jzy.gulimall.seckill.to.SeckillSkuRedisTo;
import com.jzy.gulimall.seckill.util.RedisUtil;
import com.jzy.gulimall.seckill.vo.SeckillSessionsSkusVo;
import com.jzy.gulimall.seckill.vo.SkuInfoVo;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @author jzy
 * @date 2020/9/23 19:09
 */
@Service
public class SeckillServiceImpl implements SeckillService {

    @Resource
    private CouponFeignService couponFeignService;

    @Resource
    private RedisUtil redisUtil;

    @Resource
    private ProductFeignService productFeignService;

    @Resource
    private RedissonClient redissonClient;

    @Resource
    private RabbitTemplate rabbitTemplate;

    /**
     * 闲时上架最近三天上架的商品
     */
    @Override
    public void uploadSeckillSkuLastThreeDays() {
        //1.扫描需要参加秒杀的活动
        R r = couponFeignService.getLastThreeDaysSession();
        if (r.getCode() == 0) {
            List<SeckillSessionsSkusVo> sessionsSkusVoList = r.getData(new TypeReference<List<SeckillSessionsSkusVo>>() {
            });
            //上架商品,缓存到redis
            //1.缓存活动信息
            saveSeckillSession(sessionsSkusVoList);
            //2.缓存活动关联的商品信息
            saveSeckillSessionSkus(sessionsSkusVoList);
        }
    }

    /**
     * 查询当前时间的秒杀商品
     */
    @Override
    public List<SeckillSkuRedisTo> getCurrentSeckillSkus() {
        //1.确定当前时间的秒杀场次
        Set<String> keys = redisUtil.keys(SeckillConstant.SESSION_CACHE_PREFIX + "*");
        if (CollectionUtil.isNotEmpty(keys)) {
            long time = new Date().getTime();
            for (String key : keys) {
                String[] timeStr = key.replace(SeckillConstant.SESSION_CACHE_PREFIX, "").split("_");
                long start = Long.parseLong(timeStr[0]);
                long end = Long.parseLong(timeStr[1]);
                //秒杀已经开始
                if (time >= start && time <= end) {
                    //找到秒杀场次,获取对应的秒杀商品keys
                    List<Object> keyRange = redisUtil.lGet(key, 0, -1);
                    //获取对应的秒杀商品
                    List<Object> list = redisUtil.hmultiGet(SeckillConstant.SKU_CACHE_PREFIX, keyRange);
                    if (CollectionUtil.isNotEmpty(list)) {
                        return list.stream().map(item -> {
                            System.out.println(item);
                            return (SeckillSkuRedisTo) item;
                        }).collect(Collectors.toList());
                    }
                    break;
                }
            }
        }
        return null;
    }

    /**
     * 查询当前商品的秒杀信息
     */
    @Override
    public SeckillSkuRedisTo getSkuSeckillInfo(Long skuId) {
        //redis结构:  hash  seesionId_skuId   skuInfo
        //找到所有需要参加秒杀的商品key
        Set<String> keys = redisUtil.hmgetKeys(SeckillConstant.SKU_CACHE_PREFIX);
        if (CollectionUtil.isNotEmpty(keys)) {
            String regx = "\\d*_" + skuId;
            for (String key : keys) {
                if (Pattern.matches(regx, key)) {
                    //假设一个商品只有一个秒杀信息
                    SeckillSkuRedisTo to = (SeckillSkuRedisTo) redisUtil.hget(SeckillConstant.SKU_CACHE_PREFIX, key);
                    long current = new Date().getTime();
                    Long startTime = to.getStartTime();
                    Long endTime = to.getEndTime();
                    //处理随机码
                    if (current < startTime || current > endTime) {
                        to.setRandomCode(null);
                    }
                    return to;
                }
            }
        }
        return null;
    }

    // TODO: 2020/9/24 上架秒杀商品,每一个数据都应该有过期时间
    // TODO: 2020/9/24 收货地址的获取  活动结束解锁库存
    /**
     * 处理秒杀请求
     */
    @Override
    public String kill(String killId, String key, Integer num) {
        //1.获取秒杀商品的详细信息
        SeckillSkuRedisTo to = (SeckillSkuRedisTo) redisUtil.hget(SeckillConstant.SKU_CACHE_PREFIX, killId);
        if (to != null) {
            //校验合法性
            long start = to.getStartTime();
            long end = to.getEndTime();
            long current = new Date().getTime();
            //校验时间合法性
            if (current >= start && current <= end) {
                //校验随机码
                if (to.getRandomCode().equals(key)) {
                    //校验购物数量
                    if (num <= to.getSeckillLimit().intValue()) {
                        //验证这个人是否已经买过了,幂等性处理. userId_SessionId_skuId
                        MemberRespVo memberRespVo = UserLoginInterceptor.loginUser.get();
                        String redisKey = memberRespVo.getId() + "_" + to.getPromotionSessionId() + "_" + to.getSkuId();
                        boolean setnx = redisUtil.setnx(redisKey, num, end - start, TimeUnit.MILLISECONDS);
                        if (setnx) {
                            //信号量
                            RSemaphore semaphore = redissonClient.getSemaphore(SeckillConstant.SKU_STOCK_SEMAPHORE + key);
                            try {
                                boolean bool = semaphore.tryAcquire(num, 100, TimeUnit.MILLISECONDS);
                                //秒杀成功
                                if (bool) {
                                    SeckillOrderTo seckillOrderTo = new SeckillOrderTo();
                                    String orderSn = IdUtil.getSnowflake(1, 1).nextId() + "";
                                    seckillOrderTo.setOrderSn(orderSn);
                                    seckillOrderTo.setNum(num);
                                    seckillOrderTo.setMemberId(memberRespVo.getId());
                                    seckillOrderTo.setPromotionSessionId(to.getPromotionSessionId());
                                    seckillOrderTo.setSkuId(to.getSkuId());
                                    seckillOrderTo.setSeckillPrice(to.getSeckillPrice());
                                    //快速下单,发送消息到mq
                                    rabbitTemplate.convertAndSend(SeckillConstant.ORDER_EVENT_EXCHANGE, SeckillConstant.SECKILL_CREAT_ROUTING_KEY, seckillOrderTo);
                                    return orderSn;
                                }
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        }
        return null;
    }

    //2.缓存活动关联的商品信息
    private void saveSeckillSessionSkus(List<SeckillSessionsSkusVo> sessionsSkusVoList) {
        if (CollectionUtil.isNotEmpty(sessionsSkusVoList)) {
            sessionsSkusVoList.forEach(session -> {
                session.getRelationEntities().forEach(seckillSkuVo -> {
                    //幂等性保证
                    if (!redisUtil.hHasKey(SeckillConstant.SKU_CACHE_PREFIX, seckillSkuVo.getPromotionSessionId().toString() + "_" + seckillSkuVo.getSkuId().toString())) {
                        SeckillSkuRedisTo skuRedisTo = new SeckillSkuRedisTo();
                        //sku秒杀信息
                        BeanUtil.copyProperties(seckillSkuVo, skuRedisTo);
                        //sku详细信息
                        R skuInfo = productFeignService.getSkuInfo(seckillSkuVo.getSkuId());
                        if (skuInfo.getCode() == 0) {
                            SkuInfoVo skuInfoVo = skuInfo.getData("skuInfo", new TypeReference<SkuInfoVo>() {
                            });
                            skuRedisTo.setSkuInfoVo(skuInfoVo);
                        }
                        //设置当前商品的秒杀开始和结束时间
                        skuRedisTo.setStartTime(session.getStartTime().getTime());
                        skuRedisTo.setEndTime(session.getEndTime().getTime());
                        String token = UUID.randomUUID().toString(true);
                        //设置一个随机码
                        skuRedisTo.setRandomCode(token);
                        //redis保存
                        redisUtil.hset(SeckillConstant.SKU_CACHE_PREFIX, seckillSkuVo.getPromotionSessionId().toString() + "_" + seckillSkuVo.getSkuId().toString(), skuRedisTo);

                        //使用库存作为分布式信号量  限流
                        RSemaphore semaphore = redissonClient.getSemaphore(SeckillConstant.SKU_STOCK_SEMAPHORE + token);
                        semaphore.trySetPermits(seckillSkuVo.getSeckillCount().intValue());
                    }
                });
            });
        }

    }

    //1.缓存活动信息
    private void saveSeckillSession(List<SeckillSessionsSkusVo> sessionsSkusVoList) {
        if (CollectionUtil.isNotEmpty(sessionsSkusVoList)) {
            sessionsSkusVoList.forEach(session -> {
                long startTime = session.getStartTime().getTime();
                long endTime = session.getEndTime().getTime();
                String key = SeckillConstant.SESSION_CACHE_PREFIX + startTime + "_" + endTime;
                //幂等性保证
                boolean hasKey = redisUtil.hasKey(key);
                if (!hasKey) {
                    List<Object> collect = session.getRelationEntities().stream().map(skuVo -> {
                        return skuVo.getPromotionSessionId().toString() + "_" + skuVo.getSkuId().toString();
                    }).collect(Collectors.toList());
                    redisUtil.lSet(key, collect);
                }
            });
        }
    }
}
